using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using Microsoft.Xna.Framework.Input;
using Xfinity.Engine.Plugins;

namespace Xfinity.Console
{

    #region Extra stuff.

    /// <summary>
    /// Binds a function to a command name.
    /// </summary>
    [AttributeUsage(AttributeTargets.Method)]
    public sealed class BindCommandAttribute : Attribute
    {
        private string commandName;

        /// <summary>
        /// Gets or sets the name of the command.
        /// </summary>
        /// <value>The name of the command.</value>
        public string CommandName
        {
            get { return commandName; }
            set { commandName = value; }
        }
        private Keys boundKey;

        /// <summary>
        /// Gets or set the bound key.
        /// </summary>
        public Keys BoundKey
        {
            get { return boundKey; }
            set { boundKey = value; }
        }


        /// <summary>
        /// Initializes a new instance of the <see cref="BindCommandAttribute"/> class.
        /// </summary>
        /// <param name="commandName">Name of the command.</param>
        public BindCommandAttribute(string commandName) : this(Keys.None, commandName) { }

        public BindCommandAttribute(Keys boundKey, string commandName)
        {
            this.boundKey = boundKey;
            this.commandName = commandName;
        }

    }


    /// <summary>
    /// A callback that processes and executes it.
    /// </summary>
    /// <param name="command">The command and its parameters.</param>
    public delegate void ExecuteCommandFromString(string[] command);

    /// <summary>
    /// Thrown when a command can't be executed.
    /// </summary>
    [Serializable]
    public class CommandFailedException : Exception
    {
        private string[] commands;

        /// <summary>
        /// Gets the commands.
        /// </summary>
        /// <value>The commands.</value>
        public string[] Commands
        {
            get { return commands; }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CommandFailedException"/> class.
        /// </summary>
        /// <param name="commands">The commands.</param>
        public CommandFailedException(string[] commands)
        {
            this.commands = commands;
        }
    }

    #endregion

    /// <summary>
    /// Parses the commands to execute from strings.
    /// </summary>
    public static class CommandParser
    {
        /// <summary>
        /// Registers a command.
        /// </summary>
        /// <param name="commandName">Name of the command.</param>
        /// <param name="command">The command delegate.</param>
        public static void RegisterCommand(string commandName, ExecuteCommandFromString command)
        {
            if (commands.ContainsKey(commandName.ToLower()))
            {
                System.Diagnostics.Debug.Fail("Command already bound.");
                return;
            }
            commands.Add(commandName.ToLower(), command);
        }

        /// <summary>
        /// Processes a command.
        /// </summary>
        /// <param name="commandLine">The command line.</param>
        public static void ProcessCommand(string commandLine)
        {
            string[] splitCommands = commandLine.Split(' ');
            try
            {
                ExecuteCommandFromString callback = commands[splitCommands[0].ToLower()];
                callback(splitCommands);
            }
            catch (KeyNotFoundException)
            {
                throw new CommandFailedException(splitCommands);
            }
        }

        private static Dictionary<string, ExecuteCommandFromString> commands =
            new Dictionary<string, ExecuteCommandFromString>();

        /// <summary>
        /// The list of commands and their processors.
        /// </summary>
        /// <value>The commands.</value>
        public static Dictionary<string, ExecuteCommandFromString> Commands
        {
            get { return commands; }
        }

        /// <summary>
        /// Scans all loaded assemblies for bound commands.
        /// </summary>
        public static void ScanForBoundCommands()
        {
#if!XBOX
            ScanForBoundCommands(AppDomain.CurrentDomain.GetAssemblies());
#else
            Assembly[] a = {Assembly.GetCallingAssembly(),Assembly.GetExecutingAssembly()};
            ScanForBoundCommands(a);
#endif
            
        }

        /// <summary>
        /// Scans for bound commands in a list of assemblies.
        /// </summary>
        /// <param name="assemblies">The assemblies.</param>
        public static void ScanForBoundCommands(IEnumerable<Assembly> assemblies)
        {
            foreach (Assembly a in assemblies)
            {
                foreach (Type t in a.GetTypes())
                {
                    foreach (MethodInfo method in t.GetMethods(BindingFlags.NonPublic|BindingFlags.Public|BindingFlags.Instance|BindingFlags.Static))
                    {
                        BindCommandAttribute[] bca =
                            (BindCommandAttribute[])method.GetCustomAttributes(typeof(BindCommandAttribute), false);
                        foreach (BindCommandAttribute attr in bca)
                        {
                            System.Diagnostics.Debug.WriteLine(attr.CommandName + " is bound to " + method.Name, "Bound command found");
                            RegisterCommand(attr.CommandName, (ExecuteCommandFromString)
                                ExecuteCommandFromString.CreateDelegate(typeof(ExecuteCommandFromString),
                                    null,
                                    method));

                            if (attr.BoundKey != Keys.None)
                            {
                                InputHandler.BindToCommand(attr.BoundKey, attr.CommandName);
                            }
                        }
                    }
                }
            }
        }
    }
}